Skip to content

fix(spec): register sys_package_version in CLOUD_PROVIDED_OBJECT_NAMES - #17214

Merged
os-bill merged 4 commits into
mainfrom
claude/issue-16745-cloud-provided-package-version
Sep 9, 2026
Merged

fix(spec): register sys_package_version in CLOUD_PROVIDED_OBJECT_NAMES#17214
os-bill merged 4 commits into
mainfrom
claude/issue-16745-cloud-provided-package-version

Conversation

@os-bill

@os-bill os-bill commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16745

CLOUD_PROVIDED_OBJECT_NAMES carried sys_package and sys_package_installation — the head and tail of the three-table package family packages/spec/src/cloud/package.zod.ts declares — but not sys_package_version, the release-snapshot table between them, whose row schema this repository ships as cloud/package-version.zod.ts. Platform metadata that ships with the product references the name (sys_metadata.package_version_id is a Field.lookup('sys_package_version', …) in @objectstack/metadata-core), so against the list's own stated purpose that shipped lookup target was being judged a platform-prefixed name nothing registers.

This PR adds the one entry and pins it by name in the constant's test, beside the sys_license pin that landed for the same class of drift (#13842). Nothing under packages/lint moves: the reference ladder is correct, the list was short.

Clause-②: yes

CLOUD_PROVIDED_OBJECT_NAMES is a closed accept set and this diff adds a member to it, so the lint ladder now accepts a name it previously warned on. That is a widening, and it takes yes. node scripts/pm/check-clause2-carriers.mjs --pair 17214 fires C5/T2 at platform-object-names.ts:156 — "a new member of a closed set" — and the declaration now agrees with it.

An earlier revision of this body declared no, citing the list's header as contract text that already promised the name. The domain:spec seat withdrew that reading: the header states why the list exists, not that sys_package_version is a member of it, and a purpose is not an enumeration. The carve-out it invoked — deleting a refusal the published text itself denies — therefore does not apply, and no machine-readable waiver exists for a prose argument in its place. needs:contract-review is hung on both carriers and is the review seat's to clear; this PR waits outside the queue until it is.

⚠️ --pair still exits 4 on this head, and the reason is a carrier this PR cannot reach. C5 reads the declaration limb from the card's governing claim comment (cardDeclaration() over the card's comment thread), never from a PR body — the level axis in check-changeset-no-major.mjs is the reader that takes the PR body, and that is the one this edit serves. Card #16745's claim comment still carries a line-start Clause-②: no, so C5 reports the pair as illegible until the claiming seat re-declares it there. The checker's own header forbids anyone else from doing that on the seat's behalf — "the declaration IS the judgement" — so it is left, reported, and not written around.

Premise re-derived on origin/main, then re-derived again after the merge

  • Before the fix, at origin/main: git show origin/main:…/platform-object-names.ts listed sys_app, sys_environment, sys_environment_member, sys_license, sys_package, sys_package_installation — six members, sys_package_version absent, head and tail of the family present.
  • git grep sys_package_version origin/main -- packages/metadata-core/src/objects/sys-metadata.object.ts → line 76 package_version_id: Field.lookup('sys_package_version', { still present.

Both halves held; nothing had been landed since triage. origin/main was merged in twice through scripts/pm/os-regen-merge.sh — merge commits 87751f84e and, this round, df37e6564 — with no os-regen path needing a side taken and no generated artifact moving either time.

Changeset — @objectstack/spec at minor, derived between a floor and a ceiling

platform-object-names.ts is not a .zod.ts, so it does not ship as source under files[] — but src/system/constants/index.ts re-exports it (export * from './platform-object-names'), so the constant ships compiled inside dist, which is in files[]. Measured after the rebuild, with controls:

built entry sys_package_version control sys_license neg. control
dist/system/index.mjs 1 1 0
dist/system/index.js 1 1 0
dist/index.mjs 1 1 0
dist/browser/system/index.mjs 1 1 0

api-surface/system.json and export-origins/system.json both list CLOUD_PROVIDED_OBJECT_NAMES. So published behaviour moves ⇒ a changeset is owed and skip-changeset does not apply. The level was derived, not taken on anyone's word, and the floor and the ceiling meet at one value:

  • Floor — the act. pr-automation.yml's Check Changeset prose ("WHICH LEVEL", maintainer ruling 2026-09-04, batch [WIP] Add query enhancements and advanced validation features #35 on finding(changeset): two independent contract reviews read the repo's own history to opposite bumps for "add an exported symbol to a published index" #15294): "a purely additive widening of a published package's public surface (a new exported symbol on an index, a new accepted key or value) takes at least minor". A new accepted value is exactly what this is.
  • Floor — the declaration. The level axis in check-changeset-no-major.mjs: a PR declaring clause ② "must grade AT LEAST ONE package whose published source it moves minor or above". @objectstack/spec is the only package this diff moves, so it is that package. patch across the board is verdict enforce, exit 1 — the red this grade change exists to prevent.
  • Ceiling. major is refused outright by the same script's launch-window guard, and no allow-major label is on this PR. .changeset/pre.json is absent, so the RC exemption that would stand the guard down is not in effect either.
  • Residue. minor is therefore the only legal grade, and it is also the semantically right one: nothing is removed or narrowed, so no member's meaning changes for an existing consumer. No ADR-0087 disposition is owed — that gate reads breaking signals, which are a major bump or a **BREAKING / BREAKING CHANGE marker in the body, and this changeset carries neither.

Locally the level axis reports NOT APPLICABLE ("no pull_request payload to read a declaration from") because the declaration it reads is PR-scoped; its real reading is CI's Check Changeset on this PR. The three changeset gates that DO have a local reading on df37e6564 all pass and print their own verdicts:

check-changeset-no-major   ✓ This diff introduces no `major` bump.       (base bccf31110)
check-empty-changeset      ✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
check-adr-0087-registration ✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).

Verification — final head df37e6564

Heavy runs went through bash scripts/pm/os-verify-lock.sh on slot issue-16745-resume.

The full package suite, in one run, no --project filter and no file list — so both vitest projects (local and repo) are selected by the config, not by me:

pnpm --filter @objectstack/spec exec vitest run --reporter=verbose --maxWorkers=2
  Test Files  498 passed (498)
       Tests  13609 passed (13609)
    Duration  561.78s
os-verify-lock: VERDICT command-exit 0 · held the lock 563s (9m23s) · waited 0s
  ⚠ SHARED-BOX SECONDS — this lock excluded other LOCKED runs, NOT unlocked sibling work

Re-taken on df37e6564 after this round's merge, ⛔ not carried over from the previous head.

Proof the pin file was actually in that run — the reason this matters is that platform-object-names.test.ts is one of the 28 entries in vitest.repo-tests.json, so the local project excludes it and pnpm test alone would never have run it. From the verbose reporter's own output, all nine cases of the file, tagged with the project that ran them:

✓ |repo| src/system/constants/platform-object-names.test.ts > PLATFORM_PROVIDED_OBJECT_NAMES — … > finds the platform object sources (guards against a broken scan)
✓ |repo| src/system/constants/platform-object-names.test.ts > … > registers exactly the objects each package declares
✓ |repo| src/system/constants/platform-object-names.test.ts > … > has no registry group for a package that declares no objects
✓ |repo| src/system/constants/platform-object-names.test.ts > … > every registered name carries a reserved platform prefix
✓ |repo| src/system/constants/platform-object-names.test.ts > … > cloud-only names are registered but not declared in this repo
✓ |repo| src/system/constants/platform-object-names.test.ts > platform-object predicates > separates a real platform object from a fictional one
✓ |repo| src/system/constants/platform-object-names.test.ts > platform-object predicates > treats an unprefixed name as neither
✓ |repo| src/system/constants/platform-object-names.test.ts > platform-object predicates > resolves a cloud-provided object this repo never declares
✓ |repo| src/system/constants/platform-object-names.test.ts > platform-object predicates > resolves the middle table of the cloud package family

The last line is this PR's new case. The line above it is the pre-existing sys_license sibling, lit in the same run as the control.

Ablation — the pin can fail, and fails for the right reason. Direction predicted before running: turns red. It was run on head 87751f84e and is ⛔ not re-run here, because the two files it exercises are byte-identical at the final head — platform-object-names.ts hashes f691ad7939dac77330995e3d9db49e95182ad5db and its test b2a4cde719761206871181f1ff358753b2371ab9 at BOTH 87751f84e and df37e6564, and this round's diff is one .changeset/*.md. Both legs ran under the lock, each proving its edit reached disk before the run was read:

HEAD blob:                        f691ad7939dac77330995e3d9db49e95182ad5db
marker count BEFORE mutation: 1   (grep -cP "^  'sys_package_version',$")
marker count AFTER mutation:  0   → mutated file hashes to 6aff77525b048fc1662793c99e3c3a2f140f3e17,
                                    byte-identical to the pre-fix blob this PR replaced
MUTATED  LEG exit 1 → Test Files 1 failed (1) · Tests 1 failed | 8 passed (9)
         AssertionError: sys_package_version: expected false to be true
restored hash: f691ad7939dac77330995e3d9db49e95182ad5db (matches HEAD) · `git diff HEAD` empty
RESTORED LEG exit 0 → Test Files 1 passed (1) · Tests 9 passed (9)

Exactly one of the nine cases moved, and it is the new one — the pin is load-bearing and precisely scoped. The restore is anchored at HEAD, not a bare checkout, and is proven by hash equality plus an empty git diff HEAD, not by an exit code.

Other package-level runs, same lock acquisition, exit codes landed to disk before being read:

  • pnpm --filter @objectstack/spec buildBUILD_EXIT=0
  • pnpm --filter @objectstack/spec typecheck (tsc + scripts tsconfig + check:test-typecheck) → TYPECHECK_EXIT=0. Both exit codes were echoed individually rather than read from the lock wrapper's batch verdict, which covers only the last part of a ;-sequenced command and says so.
  • pnpm --filter @objectstack/spec check:generated → exit 0, and git status clean — no generated artifact moved. Consistent with the mechanism: nothing outside packages/spec reads CLOUD_PROVIDED_OBJECT_NAMES, and scripts/platform-object-tenancy-census.json contains no sys_package row at all.
  • ① dependency closure: @objectstack/spec declares no @objectstack/* workspace dependency, so pnpm --filter '@objectstack/spec^...' build is empty by construction; the package itself was built.

Gate families ③/④ — derived mechanically, never from a hand-fed path list:

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
  → gate list derived from the tree of 'objectstack-ai/objectstack' at commit df37e6564
  → change set derived from git — 3 path(s) vs merge base bccf31110 (three-dot)
  → 77 command(s)

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.txt
  ✓ 77 derived famil(ies) accounted for — 77 run, 0 NOT-MEASURED, 0 UNRUN.

73 exited 0. Four exited 3 = PREREQUISITE NOT MET, recorded as NOT MEASURED — neither green nor red — all four because they read built output for packages this worktree has not built, which CI builds fresh:

family unmet prerequisite (quoted from the gate)
@objectstack/lint check:doc-formula-expressions "the workspace package @objectstack/formula is not built"
check:dual-build-cjs-loads "this gate reads built output, and some package has no dist/"
check:lean-entry-closure "this gate loads BUILT entry points" — packages/objectql/dist/core.mjs absent
check:type-check-debt measuring without the closure "would silently measure a DIFFERENT WORLD"

check:nul-bytes exited 0, and a separate grep -naP sweep for control characters over the three changed files found none. Repo-wide pnpm lint is CI's run and was not attempted here.

Not measured, by design of the contract

That @objectstack/service-tenant actually registers sys_package_version lives in the cloud repository, which is not reachable from this session. The list's header says that half is owned there and cannot be conformance-tested from this repo. It is NOT MEASURED here — not verified, and not substituted with anything this repository can prove instead. Reported on the card as an out-of-scope finding for the seat to file cross-repo.

验收备注

  • Sibling-absence sweep (scope fence: report, do not widen). Re-run on the rebuilt dist against PLATFORM_PROVIDED_OBJECT_NAMES: 112 shipped *.object.ts files under packages/ + examples/, 135 reference targets, 109 of them platform-prefixed ⇒ 0 unregistered. Controls: sys_user appears as a target 61 times (lit), registry.has('sys_definitely_not_real') is false, registry.has('sys_package_version') is true — the last also proving the dist read was the rebuilt one. This reproduces the card's measurement, which found exactly one finding in the whole universe, and brings it to zero.
  • noted, not filed: packages/spec/src/cloud/environment.zod.ts names sys_environment_credential as a control-plane table and tenant.zod.ts names the deprecated sys_tenant_database. Neither is declared here nor listed in CLOUD_PROVIDED_OBJECT_NAMES, and both are referenced by zero shipped *.object.ts files, so no false refusal reproduces today — which is what separates them from this card. Whether the cloud runtime registers them is the same unmeasurable half as above. 承接者: the seat filing the cross-repo card carries the question alongside.

Generated by Claude Code


Generated by Claude Code

The curated cloud list carried sys_package and sys_package_installation
but not the release-snapshot table between them, so the shipped
sys_metadata.package_version_id lookup target was classed as a
platform-prefixed name nothing registers. Add the one entry, pin it by
name beside sys_license, and add the patch changeset.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:system tests tooling labels Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 2 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via sys_package_version (literal, a string literal in CLOUD_PROVIDED_OBJECT_NAMES))
  • content/docs/deployment/publish-and-preview.mdx (via sys_package_version (literal, a string literal in CLOUD_PROVIDED_OBJECT_NAMES))
What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 134 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json bccf311100cad7affccd6cbfcddbd81fe734d97dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 27b16955a2361c440efae489eac4948cecf7b2c1 — the merge of head df37e65647f1100a615485a8850a6db549cc7afb into base bccf311100cad7affccd6cbfcddbd81fe734d97d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 27b16955a2361c440efae489eac4948cecf7b2c1 && git checkout 27b16955a2361c440efae489eac4948cecf7b2c1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bccf311100cad7affccd6cbfcddbd81fe734d97d df37e65647f1100a615485a8850a6db549cc7afb && git checkout -B drift-repro bccf311100cad7affccd6cbfcddbd81fe734d97d && git merge --no-ff df37e65647f1100a615485a8850a6db549cc7afb

node scripts/docs-audit/affected-docs.mjs --json bccf311100cad7affccd6cbfcddbd81fe734d97d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs bccf311100cad7affccd6cbfcddbd81fe734d97d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

The clause-② declaration for this PR is `yes`: `CLOUD_PROVIDED_OBJECT_NAMES`
is a closed accept set and this diff adds a member to it. A PR that declares
clause ② must grade at least one package whose published source it moves at
`minor` or above (`check-changeset-no-major.mjs`, level axis), and a purely
additive widening of a published public surface takes at least `minor` by the
`Check Changeset` "WHICH LEVEL" rule. `major` is refused by the same script's
launch-window guard, so `minor` is the only legal grade.

The changeset prose no longer frames the addition as restoring something the
published header already promised — that reading was withdrawn: the header
states why the list exists, not that this name is a member of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
@os-bill
os-bill marked this pull request as ready for review September 9, 2026 22:16
@os-bill
os-bill added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 1555ed4 Sep 9, 2026
45 checks passed
@os-bill
os-bill deleted the claude/issue-16745-cloud-provided-package-version branch September 9, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/s tests tooling

Projects

None yet

2 participants